/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ai;

import comm.Messenger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import plot.GNUPlot;
import search.AStarSearch;
import search.BreadthFirstSearch;
import search.DepthFirstSearch;
import search.GreedyBestSearch;
import search.Search;
import search.UniformCostSearch;
import search.WorldNode;
import world.Enemy;
import world.Flag;
import world.Friend;
import world.Obstacle;
import world.Point;
import world.World;

/**
 *
 * @author dheath
 */
public class Searching implements Intelligence
{

    private int botId;
    private boolean penalized;
    private int nodeSize;
    private World world;
    private Messenger msgr;
    private Search search;
    private WorldNode start;
    private WorldNode end;
    
    private double plotSpeed;
    
    public Searching(int botId)
    {
        this.botId = botId;

        try
        {
            InputStreamReader isr = new InputStreamReader(System.in);
            BufferedReader stdin = new BufferedReader(isr);

            System.out.println("How small are the nodes?");
            String size = stdin.readLine();
            nodeSize = Integer.parseInt(size);

            System.out.println("Will this be a penalized search?");
            String ans = stdin.readLine();
            if (ans.equalsIgnoreCase("y"))
            {
                this.penalized = true;
            } else
            {
                this.penalized = false;
            }

            while (true)
            {
                System.out.println("Which search algorithm?");
                System.out.println("1 - Depth-first");
                System.out.println("2 - Breadth-first");
                System.out.println("3 - Uniform Cost");
                System.out.println("4 - Greed Best");
                System.out.println("5 - A*");

                String srch = stdin.readLine();
                int searchv = 0;
                try
                {
                    searchv = Integer.parseInt(srch);
                } catch (NumberFormatException ex)
                {
                    System.out.println("choice must be a number from 1 to 5");
                    continue;
                }
                if (searchv > 0 && searchv < 6)
                {
                    search = getSearchAlgorithm(searchv);
                    break;
                }
            }
            
             System.out.println("Animation delay?");
            String del = stdin.readLine();
            plotSpeed = Double.parseDouble(del);


        //stdin.close();
        } catch (IOException e)
        {
            e.printStackTrace();
        }

    }

    private Search getSearchAlgorithm(int choice)
    {
        switch (choice)
        {
            case 1:
                return new DepthFirstSearch();
            case 2:
                return new BreadthFirstSearch();
            case 3:
                return new UniformCostSearch();
            case 4:
                return new GreedyBestSearch();
            case 5:
                return new AStarSearch();
            default:
                return new AStarSearch();
        }
    }

    public void setWorld(World world)
    {
        this.world = world;
    }

    public void setMessenger(Messenger msgr)
    {
        this.msgr = msgr;
    }

    public void NextMove()
    {
        Friend me = world.getFriends().get(botId);
        GNUPlot plot = new GNUPlot(world, false, true, "worldmap.plt", plotSpeed);
        buildSearchGraph(me, plot);


        search.searchWorld(start, end, plot);
        plot.closeConnection();
    }

    private void buildSearchGraph(Friend me, GNUPlot plot)
    {
        double worldsize = Double.parseDouble(world.getConstants().get("worldsize"));
        int row = (int) (worldsize / nodeSize);

        List<WorldNode> nodes = new ArrayList();
        for (double y = (worldsize / 2.0); y > (-1 * worldsize / 2.0); y -= nodeSize)
        {
            for (double x = (-1.0 * worldsize / 2.0); x < (worldsize / 2.0); x += nodeSize)
            {

                WorldNode node = new WorldNode(new Point(x, y), nodeSize);

                nodes.add(node);
            }
        }

        for (int i = 0; i < nodes.size(); i++)
        {
            WorldNode curNode = nodes.get(i);

            //plot.printNode(curNode.getCenter(), 1);

            if (i < row)
            {
                if (i % row == 0)
                {
                    curNode.setEast(nodes.get(i + 1));
                    curNode.setSouthEast(nodes.get(i + row + 1));
                } else
                {
                    if (i % row == row - 1)
                    {
                        curNode.setWest(nodes.get(i - 1));
                        curNode.setSouthWest(nodes.get(i + row - 1));
                    } else
                    {
                        curNode.setEast(nodes.get(i + 1));
                        curNode.setSouthEast(nodes.get(i + row + 1));
                        curNode.setWest(nodes.get(i - 1));
                        curNode.setSouthWest(nodes.get(i + row - 1));
                    }
                }
                curNode.setSouth(nodes.get(i + row));
            } else
            {
                if (i >= nodes.size() - row)
                {
                    if (i % row == 0)
                    {
                        curNode.setNorthEast(nodes.get(i - row + 1));
                        curNode.setEast(nodes.get(i + 1));
                    } else
                    {
                        if (i % row == row - 1)
                        {
                            curNode.setNorthWest(nodes.get(i - row - 1));
                            curNode.setWest(nodes.get(i - 1));
                        } else
                        {
                            curNode.setNorthEast(nodes.get(i - row + 1));
                            curNode.setEast(nodes.get(i + 1));
                            curNode.setNorthWest(nodes.get(i - row - 1));
                            curNode.setWest(nodes.get(i - 1));
                        }
                    }
                    curNode.setNorth(nodes.get(i - row));
                } else
                {
                    if (i % row == 0)
                    {
                        curNode.setNorthEast(nodes.get(i - row + 1));
                        curNode.setEast(nodes.get(i + 1));
                        curNode.setSouthEast(nodes.get(i + row + 1));
                    } else
                    {
                        if (i % row == row - 1)
                        {
                            curNode.setWest(nodes.get(i - 1));
                            curNode.setSouthWest(nodes.get(i + row - 1));
                            curNode.setNorthWest(nodes.get(i - row - 1));
                        } else
                        {
                            curNode.setWest(nodes.get(i - 1));
                            curNode.setSouthWest(nodes.get(i + row - 1));
                            curNode.setNorthWest(nodes.get(i - row - 1));
                            curNode.setNorthEast(nodes.get(i - row + 1));
                            curNode.setEast(nodes.get(i + 1));
                            curNode.setSouthEast(nodes.get(i + row + 1));
                        }
                    }
                    curNode.setNorth(nodes.get(i - row));
                    curNode.setSouth(nodes.get(i + row));
                }
            }

            curNode.setCost(getCost(curNode));
            setStart(curNode, me);
            setEnd(curNode, me);

        }

        //plotWorld(nodes, plot);
    }

    private void plotWorld(List<WorldNode> nodes, GNUPlot plot)
    {
        for (WorldNode node : nodes)
        {
            if (node.getCost() < Double.POSITIVE_INFINITY)
            {
                plot.printX(node.getCenter(), 2);
            } else
            {
                plot.printX(node.getCenter(), 1);
            }
        }
    }

    private void setStart(WorldNode node, Friend me)
    {
        //System.out
        if (me.getLocation().getX() >= node.getTopLeft().getX() && me.getLocation().getY() <= node.getTopLeft().getY() &&
                me.getLocation().getX() < node.getTopLeft().getX() + nodeSize && me.getLocation().getY() > node.getTopLeft().getY() - nodeSize)
        {
            //node.setCost(0);
            start = node;
        }
    }

    private void setEnd(WorldNode node, Friend me)
    {
        for (Flag flag : world.getFlags())
        {
            if (!flag.getOwnerColor().equalsIgnoreCase(me.getColor()))
            {
                if (flag.getLocation().getX() >= node.getTopLeft().getX() && flag.getLocation().getY() <= node.getTopLeft().getY() &&
                        flag.getLocation().getX() < node.getTopLeft().getX() + nodeSize && flag.getLocation().getY() > node.getTopLeft().getY() - nodeSize)
                {
                    node.setCost(0);
                    end = node;
                }
            }
        }
    }

    private boolean insideObstacle(Obstacle ob, Point p)
    {
        boolean c = false;

        if ((((ob.getTLPoint().getY() <= p.getY()) && (p.getY() < ob.getBLPoint().getY())) ||
                ((ob.getBLPoint().getY() <= p.getY()) && (p.getY() < ob.getTLPoint().getY()))) &&
                (p.getX() < (ob.getBLPoint().getX() - ob.getTLPoint().getX()) * (p.getY() - ob.getTLPoint().getY()) / (ob.getBLPoint().getY() - ob.getTLPoint().getY()) + ob.getTLPoint().getX()))
        {
            c = !c;
        }
        if ((((ob.getTRPoint().getY() <= p.getY()) && (p.getY() < ob.getTLPoint().getY())) ||
                ((ob.getTLPoint().getY() <= p.getY()) && (p.getY() < ob.getTRPoint().getY()))) &&
                (p.getX() < (ob.getTLPoint().getX() - ob.getTRPoint().getX()) * (p.getY() - ob.getTRPoint().getY()) / (ob.getTLPoint().getY() - ob.getTRPoint().getY()) + ob.getTRPoint().getX()))
        {
            c = !c;
        }
        if ((((ob.getBRPoint().getY() <= p.getY()) && (p.getY() < ob.getTRPoint().getY())) ||
                ((ob.getTRPoint().getY() <= p.getY()) && (p.getY() < ob.getBRPoint().getY()))) &&
                (p.getX() < (ob.getTRPoint().getX() - ob.getBRPoint().getX()) * (p.getY() - ob.getBRPoint().getY()) / (ob.getTRPoint().getY() - ob.getBRPoint().getY()) + ob.getBRPoint().getX()))
        {
            c = !c;
        }
        if ((((ob.getBLPoint().getY() <= p.getY()) && (p.getY() < ob.getBRPoint().getY())) ||
                ((ob.getBRPoint().getY() <= p.getY()) && (p.getY() < ob.getBLPoint().getY()))) &&
                (p.getX() < (ob.getBRPoint().getX() - ob.getBLPoint().getX()) * (p.getY() - ob.getBLPoint().getY()) / (ob.getBRPoint().getY() - ob.getBLPoint().getY()) + ob.getBLPoint().getX()))
        {
            c = !c;
        }

        return c;
    }

    private double getCost(WorldNode node)
    {
        int count = 0;
        double avgDist = 0.0;
        double worldsize = Double.parseDouble(world.getConstants().get("worldsize"));
        double normalCost = nodeSize; // cost of a typical node
        for (Obstacle ob : world.getObstacles())
        {
            /*   Point c = ob.getLocation();
            Obstacle po = new Obstacle();
            po.setBLPoint(new Point(2.0*(ob.getBLPoint().getX() - c.getX()), 0.5 * (ob.getBLPoint().getY() - c.getY())));
            po.setTLPoint(new Point(0.5*(ob.getTLPoint().getX() - c.getX()), 0.5 * (ob.getTLPoint().getY() - c.getY())));
            po.setBRPoint(new Point(0.5*(ob.getBRPoint().getX() - c.getX()), 0.5 * (ob.getBRPoint().getY() - c.getY())));
            po.setTRPoint(new Point(0.5*(ob.getTRPoint().getX() - c.getX()), 0.5 * (ob.getTRPoint().getY() - c.getY())));
             */
            if (insideObstacle(ob, node.getCenter()))
            {
                return Double.POSITIVE_INFINITY;
            }
/*////////////////////////////////////////////////////////////////////////////////////////////////////////////////
            double dist = Math.sqrt(Math.pow(ob.getLocation().getX() - node.getCenter().getX(), 2) +
                    Math.pow(ob.getLocation().getY() - node.getCenter().getY(), 2));
            if (dist < nodeSize * 3)
            {
                count++;
                avgDist += dist;
            }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
            double dist = Math.sqrt(Math.pow(ob.getTLPoint().getX() - node.getCenter().getX(), 2) +
                    Math.pow(ob.getTLPoint().getY() - node.getCenter().getY(), 2));
            if (dist < nodeSize * 1)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(ob.getTRPoint().getX() - node.getCenter().getX(), 2) +
                    Math.pow(ob.getTRPoint().getY() - node.getCenter().getY(), 2));
            if (dist < nodeSize * 1)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(ob.getBRPoint().getX() - node.getCenter().getX(), 2) +
                    Math.pow(ob.getBRPoint().getY() - node.getCenter().getY(), 2));
            if (dist < nodeSize * 1)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(ob.getBLPoint().getX() - node.getCenter().getX(), 2) +
                    Math.pow(ob.getBLPoint().getY() - node.getCenter().getY(), 2));
            if (dist < nodeSize * 1)
            {
                count++;
                avgDist += dist;
            }
            
        }
        if (penalized)
        {
            
            double dist = Math.sqrt(Math.pow(worldsize/2 - node.getCenter().getX(), 2));
            if (dist < nodeSize * 2)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(-1*worldsize/2 - node.getCenter().getX(), 2));
            if (dist < nodeSize * 2)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(-1*worldsize/2 - node.getCenter().getY(), 2));
            if (dist < nodeSize * 2)
            {
                count++;
                avgDist += dist;
            }
            dist = Math.sqrt(Math.pow(worldsize/2 - node.getCenter().getY(), 2));
            if (dist < nodeSize * 2)
            {
                count++;
                avgDist += dist;
            }
            
            /*for (Enemy e : world.getEnemies())
            {
                /*if (e.getLocation().getX() >= node.getTopLeft().getX() && e.getLocation().getY() <= node.getTopLeft().getY() &&
                e.getLocation().getX() < node.getTopLeft().getX() + nodeSize && e.getLocation().getY() > node.getTopLeft().getY() - nodeSize)
                {
                System.out.println("found enemy!");
                return node.getLength()*5.0;
                }

                dist = Math.sqrt(Math.pow(e.getLocation().getX() - node.getCenter().getX(), 2) +
                        Math.pow(e.getLocation().getY() - node.getCenter().getY(), 2));
                if (dist < nodeSize * 3)
                {
                    count++;
                    avgDist += dist/10;
                }
            }*/

            avgDist = avgDist / count;
            double weight = (worldsize / 4 * (5 / avgDist));
            if (Double.isNaN(weight))
            {
                weight = 0;
            }

            //System.out.println("avgDist: "+avgDist);
            //System.out.println("weight: "+weight);
            return node.getLength() + weight;

        }
        return node.getLength();
    }
}
